/******************************************************************************
 *
 * Project:		mGameEngine
 * File:		tcpconnection.cpp
 * Author:		karooolek
 * Created on:	2009-03-14
 *
 ******************************************************************************/

#include "tcpconnection.h"
#include <errno.h>
#include <fcntl.h>

TCPConnection::TCPConnection() :
	IPConnection(), _timeout(DEFAULT_TIMEOUT)
{
}

TCPConnection::TCPConnection(const IPAddress &ipaddr) :
	IPConnection(ipaddr), _timeout(DEFAULT_TIMEOUT)
{
}

TCPConnection::~TCPConnection()
{
	close();
}

void TCPConnection::open()
{
	// already opened
	if(_state == CONN_STATE_OPENED)
	{
		return;
	}

	// close previous connection
	close();

	// create socket
	_socket = socket(PF_INET, SOCK_STREAM, 0);
	if(_socket == INVALID_SOCKET)
	{
		close();
		printf("error = %d\t%s\t\n", errno, strerror(errno));
		return;
	}
	int optval = 1;
	setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, (const char *) &optval,
			sizeof(int));

	// bind socket
	sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(_ipaddr.port);
	addr.sin_addr.s_addr = INADDR_ANY;
	if(bind(_socket, (sockaddr *) &addr, sizeof(addr)) == SOCKET_ERROR)
	{
		close();
		printf("bind error = %d\t%s\t\n", errno, strerror(errno));
		return;
	}

	// listen
	if(listen(_socket, SOMAXCONN) == SOCKET_ERROR)
	{
		close();
		printf("listen error = %d\t%s\t\n", errno, strerror(errno));
		return;
	}

	_state = CONN_STATE_OPENED;

	// wait for connection
	SOCKET newSocket;
	sockaddr_in newAddr;
	int newAddrSize = sizeof(newAddr);
	newSocket = accept(_socket, (sockaddr *) &newAddr,
			(socklen_t*) &newAddrSize); // Dodałem rzutowanie na socklen_t*	< Maciek
	if(newSocket == INVALID_SOCKET)
	{
		close();
		printf("error = %d\t%s\t\n", errno, strerror(errno));
		return;
	}

#ifdef WIN32
	closesocket(_socket);
#else
	::close(_socket);
#endif
	_socket = newSocket;

	// TODO get hostname and save in _ipaddr

	_state = CONN_STATE_CONNECTED;
}

void TCPConnection::close()
{
	// already closed
	if(_state == CONN_STATE_CLOSED)
	{
		return;
	}

	if(_socket != INVALID_SOCKET)
	{
#ifdef WIN32
		closesocket(_socket);
#else
		::close(_socket);
#endif
	}
	_socket = INVALID_SOCKET;

	_state = CONN_STATE_CLOSED;
}

void TCPConnection::connect()
{
	// close previous connection
	close();

	// create socket
	_socket = socket(PF_INET, SOCK_STREAM, 0);
	if(_socket == INVALID_SOCKET)
	{
		close();
		return;
	}

	_state = CONN_STATE_CONNECTING;

	// connect
	// TODO is it the best way???
	sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(_ipaddr.port);
	addr.sin_addr.s_addr = inet_addr(_ipaddr.hostname.c_str());

	// try to connect
	clock_t t = clock();
	while(::connect(_socket, (const sockaddr *) &addr, sizeof(addr))
			== SOCKET_ERROR)
	{
		if(_timeout >= 0.0f && (clock() - t) * 1000 / CLOCKS_PER_SEC > _timeout)
		{
			close();
			return;
		}

		_sleep(1000);
	}

	// select settings:
	FD_ZERO(&readfds);
	FD_SET(_socket, &readfds);

	_state = CONN_STATE_CONNECTED;
}

void TCPConnection::disconnect()
{
	close();
}

uint TCPConnection::send(const uint8 *data, uint size)
{
	// not connected
	if(_state != CONN_STATE_CONNECTED)
	{
		return 0;
	}

	// send data
	int res = ::send(_socket, (const char *) data, size, 0);
	if(res == SOCKET_ERROR)
	{
		// TODO check errors
	}

	return res;
}

uint TCPConnection::receive(uint8 *buffer, uint size)
{
	// not connected
	if(_state != CONN_STATE_CONNECTED)
	{
		return 0;
	}

	// set up the file descriptor set
	fd_set fds;
	FD_ZERO(&fds);
	FD_SET(_socket, &fds);

	// set up the struct timeval for the timeout
	timeval tv;
	tv.tv_sec = _timeout / 1000;
	tv.tv_usec = 0;

	// wait until timeout or data received
	int n = select(_socket+1, &fds, NULL, NULL, &tv);
	if(n == 0)
	{
		_state = CONN_STATE_LOST;
		return 0; // timeout!
	}
	if(n == -1)
	{
		_state = CONN_STATE_LOST;
		return 0;
	}

	int res = ::recv(_socket, (char *)buffer, size, 0);
	if(res <= 0)
	{
		_state = CONN_STATE_LOST;
		return 0;
	}

	return res;
}

